<?php
/**
 * QQAS - share your knowledge to the world!
 *
 * @author		xiaochong0302 <xiaochong0302@gmail.com>
 * @copyright	Copyright (c) 2011, Koogua Studio.
 * @license		http://www.qqas.org/user_guide/license.html
 * @link		http://www.qqas.org
 * @since		Version 1.0
 * @filesource
 */


/**
 * 对mysql记录中的null值进行处理
 * @access  public
 * @param   string  $str
 * @return  string
 */
function dump_null_string($str)
{
    if (!isset($str) || is_null($str))
    {
        $str = 'NULL';
    }
    return $str;
}


/**
 * @access  public
 * @param
 * @return  void
 */
function remove_comment($var)
{
    return (substr($var, 0, 2) != '--');
}

// ------------------------------------------------------------------------

/**
 * sql dump class
 *
 * @category	Util
 */
class SqlDump
{
    var $max_size  = 2097152; // 2M
    var $is_short  = false;
    var $offset    = 300;
    var $dump_sql  = '';
    var $sql_num   = 0;
    var $error_msg = '';
    var $db_version = '';
    var $db_charset = 'utf8';
    var $software_version = '';
    var $db;

    /**
     *  类的构造函数
     * @access  public
     * @param
     * @return void
     */
    function SqlDump(&$db, $max_size=0)
    {
        $this->db = &$db;
        if ($max_size > 0 )
        {
            $this->max_size = $max_size;
        }
        $this->db_version = $this->get_db_version();
        $this->db_charset = C('DB_CHARSET') ? C('DB_CHARSET') : $db_charset;
        $this->software_version = get_site_config('software_version');
    }


    /**
     *  类的构造函数
     * @access  public
     * @param
     * @return void
     */
    function __construct(&$db, $max_size =0)
    {
        $this->SqlDump($db, $max_size);
    }


    /**
     *  获取指定表的定义
     * @access  public
     * @param   string      $table      数据表名
     * @param   boolen      $add_drop   是否加入drop table
     * @return  string      $sql
     */
    function get_table_df($table, $add_drop = false)
    {
        if ($add_drop)
        {
            $table_df = "DROP TABLE IF EXISTS `$table`;\r\n";
        }
        else
        {
            $table_df = '';
        }

        $tmp_arr = $this->db->query("SHOW CREATE TABLE {$table}");
        $tmp_sql = $tmp_arr[0]['Create Table'];
        $tmp_sql = substr($tmp_sql, 0, strrpos($tmp_sql, ")") + 1); //去除行尾定义。

        if ($this->db_version >= '4.1')
        {
            $table_df .= $tmp_sql . " ENGINE=MyISAM DEFAULT CHARSET=" . str_replace('-', '', $this->db_charset) . ";\r\n";
        }
        else
        {
            $table_df .= $tmp_sql . " TYPE=MyISAM;\r\n";
        }

        return $table_df;
    }


    /**
     *  获取指定表的数据定义
     * @access  public
     * @param   string      $table      表名
     * @param   int         $pos        备份开始位置
     * @return  int         $post_pos   记录位置
     */
    function get_table_data($table, $pos)
    {
        $post_pos = $pos;

        /* 获取数据表记录总数 */
		$total = $this->db->table($table)->count();
        if ($total == 0 || $pos >= $total)
        {
            /* 无须处理 */
            return -1;
        }

        /* 确定循环次数 */
        $cycle_time = ceil(($total-$pos) / $this->offset); //每次取offset条数。需要取的次数

        /* 循环查数据表 */
        for($i = 0; $i<$cycle_time; $i++)
        {
            /* 获取数据库数据 */
            $limit = ($this->offset * $i + $pos).','.$this->offset;
            $data = $this->db->table($table)->limit($limit)->select();
            $data_count = count($data);

            $fields = array_keys($data[0]);
            $start_sql = "INSERT INTO `$table` ( `" . implode("`, `", $fields) . "` ) VALUES ";

            /* 循环将数据写入 */
            for($j=0; $j< $data_count; $j++)
            {
                $record = array_map("dump_null_string", $data[$j]);     //处理null值

                /* 检查是否能写入，能则写入 */
                if ($this->is_short)
                {
                    if ($post_pos == $total-1)
                    {
                        $tmp_dump_sql = " ( '" . implode("', '" , $record) . "' );\r\n";
                    }
                    else
                    {
                        if ($j == $data_count - 1)
                        {
                            $tmp_dump_sql = " ( '" . implode("', '" , $record) . "' );\r\n";
                        }
                        else
                        {
                            $tmp_dump_sql = " ( '" . implode("', '" , $record) . "' ),\r\n";
                        }
                    }

                    if ($post_pos == $pos)
                    {
                        /* 第一次插入数据 */
                        $tmp_dump_sql = $start_sql . "\r\n" . $tmp_dump_sql;
                    }
                    else
                    {
                        if ($j == 0)
                        {
                            $tmp_dump_sql = $start_sql . "\r\n" . $tmp_dump_sql;
                        }
                    }
                }
                else
                {
                    $tmp_dump_sql = $start_sql . " ('" . implode("', '" , $record) . "');\r\n";
                }

                $tmp_str_pos = strpos($tmp_dump_sql, 'NULL');         //把记录中null值的引号去掉
                $tmp_dump_sql = empty($tmp_str_pos) ? $tmp_dump_sql : substr($tmp_dump_sql, 0, $tmp_str_pos - 1) . 'NULL' . substr($tmp_dump_sql, $tmp_str_pos + 5);

                if (strlen($this->dump_sql) + strlen($tmp_dump_sql) > $this->max_size - 32)
                {
                    if ($this->sql_num == 0)
                    {
                        $this->dump_sql .= $tmp_dump_sql; //当是第一条记录时强制写入
                        $this->sql_num++;
                        $post_pos++;
                        if ($post_pos == $total)
                        {
                            /* 所有数据已经写完 */
                            return -1;
                        }
                    }
                    return $post_pos;
                }
                else
                {
                    $this->dump_sql .= $tmp_dump_sql;
                    $this->sql_num++; //记录sql条数
                    $post_pos++;
                }
            }
        }

        /* 所有数据已经写完 */
        return -1;
    }


    /**
     *  备份一个数据表
     * @access  public
     * @param   string      $path       保存路径表名的文件
     * @param   int         $vol        卷标
     * @return  array       $tables     未备份完的表列表
     */
    function dump_table($path, $vol)
    {
        $tables = $this->get_tables_list($path);

        if ($tables === false)
        {
            return false;
        }

        if (empty($tables))
        {
            return $tables;
        }

        $this->dump_sql = $this->make_head($vol);

        foreach($tables as $table => $pos)
        {
            if ($pos == -1)
            {
                /* 获取表定义，如果没有超过限制则保存 */
                $table_df = $this->get_table_df($table, true);
                if (strlen($this->dump_sql) + strlen($table_df) > $this->max_size - 32)
                {
                    if ($this->sql_num == 0)
                    {
                        /* 第一条记录，强制写入 */
                        $this->dump_sql .= $table_df;
                        $this->sql_num +=2;
                        $tables[$table] = 0;
                    }
                    /* 已经达到上限 */
                    break;
                }
                else
                {
                    $this->dump_sql .= $table_df;
                    $this->sql_num +=2;
                    $pos = 0;
                }
            }

            /* 尽可能多获取数据表数据 */
            $post_pos = $this->get_table_data($table, $pos);
            if ($post_pos == -1)
            {
                /* 该表已经完成，清除该表 */
                unset($tables[$table]);
            }
            else
            {
                /* 该表未完成。说明将要到达上限,记录备份数据位置 */
                $tables[$table] = $post_pos;
                break;
            }
        }
        $this->dump_sql .= '-- END Koogua SQL Dump Program ';
        $this->put_tables_list($path, $tables);

        return $tables;
    }


    /**
     *  生成备份文件头部
     * @access  public
     * @param   int     文件卷数
     * @return  string  $str    备份文件头部
     */
    function make_head($vol)
    {
        /* 系统信息 */
        $sys_info['os'] = PHP_OS;
        $sys_info['php_version'] = PHP_VERSION;
        $sys_info['mysql_version'] = $this->db_version;
        $sys_info['software_version'] = $this->software_version;
        $sys_info['date'] = date('Y-m-d H:i:s');

        $head = "-- Koogua SQL Dump Program\r\n".
                "-- DATE : ".$sys_info["date"]."\r\n".
                "-- MYSQL SERVER VERSION : ".$sys_info['mysql_version']."\r\n".
                "-- SOFTWARE VERSION : ".$sys_info['software_version']."\r\n".
                "-- PHP VERSION : ".$sys_info['php_version']."\r\n".
                "-- VOL : ".$vol."\r\n";

        return $head;
    }


    /**
     *  获取备份文件信息
     * @access  public
     * @param   string      $path       备份文件路径
     * @return  array       $arr        信息数组
     */
    function get_head($path)
    {
        /* 获取sql文件头部信息 */
        $sql_info = array('date'=>'', 'mysql_version'=>'', 'software_version'=>'', 'php_version'=>0, 'vol'=>0);
        $fp = fopen($path, 'rb');
        $str = fread($fp, 250);
        fclose($fp);
        $arr = explode("\n", $str);

        foreach ($arr AS $val)
        {
            $pos = strpos($val, ':');
            if ($pos > 0)
            {
                $type = trim(substr($val, 0, $pos), "-\n\r\t ");
                $value = trim(substr($val, $pos+1), "/\n\r\t ");
                if ($type == 'DATE')
                {
                    $sql_info['date'] = $value;
                }
                elseif ($type == 'MYSQL SERVER VERSION')
                {
                    $sql_info['mysql_version'] = $value;
                }
                elseif ($type == 'SOFTWARE VERSION')
                {
                    $sql_info['software_version'] = $value;
                }
                elseif ($type == 'PHP VERSION')
                {
                    $sql_info['php_version'] = $value;
                }
                elseif ($type == 'VOL')
                {
                    $sql_info['vol'] = $value;
                }
            }
        }

        return $sql_info;
    }


    /**
     *  将文件中数据表列表取出
     * @access  public
     * @param   string      $path    文件路径
     * @return  array       $arr    数据表列表
     */
    function get_tables_list($path)
    {
        if (!file_exists($path))
        {
            $this->error_msg = sprintf(L('path_not_exist'), $path);
            return false;
        }

        $arr = array();
        $str = @file_get_contents($path);

        if (!empty($str))
        {
            $tmp_arr = explode("\n", $str);
            foreach ($tmp_arr as $val)
            {
                $val = trim ($val, "\r;");
                if (!empty($val))
                {
                    list($table, $count) = explode(':',$val);
                    $arr[$table] = $count;
                }
            }
        }
        return $arr;
    }


    /**
     *  将数据表数组写入指定文件
     * @access  public
     * @param   string      $path    文件路径
     * @param   array       $arr    要写入的数据
     * @return  boolen
     */
    function put_tables_list($path, $arr)
    {
        if (is_array($arr))
        {
            $str = '';
            foreach($arr as $key => $val)
            {
                $str .= $key . ':' . $val . ";\r\n";
            }

            if (@file_put_contents($path, $str))
            {
                return true;
            }
            else
            {
                $this->error_msg = sprintf(L('path_not_writable'), $path);
                return false;
            }
        }
        else
        {
            $this->error_msg = 'It need an array';
            return false;
        }
    }


    /**
     *  返回一个随机的名字
     * @access  public
     * @param
     * @return      string      $str    随机名称
     */
    function get_random_name()
    {
        $str = date('Ymd');
        for ($i = 0; $i < 6; $i++)
        {
            $str .= chr(mt_rand(97, 122));
        }
        return $str;
    }


    /**
     *  返回错误信息
     * @access  public
     * @param
     * @return void
     */
    function error_msg()
    {
        return $this->error_msg;
    }


    /**
     * 返回数据库版本号
     * @access  public
     * @param
     * @return string
     */
     function get_db_version()
     {
     	 $result = $this->db->query(' SELECT version() as version' );
     	 return $result[0]['version'];
     }


    /**
     * 执行导入的sql语句
	 * @access  public
	 * @param  $sql_file
	 * @return  void
	 */
    function sql_import($sql_file)
	{
	    $db_ver  = $this->db_version;
	    $sql_str = array_filter(file($sql_file), 'remove_comment');
	    $sql_str = str_replace("\r", '', implode('', $sql_str));
	    $ret = explode(";\n", $sql_str);
	    $ret_count = count($ret);

	    /* 执行sql语句 */
	    if ($db_ver > '4.1')
	    {
	        for($i = 0; $i < $ret_count; $i++)
	        {
	            $ret[$i] = trim($ret[$i], " \r\n,;"); //剔除多余信息

	            if (!empty($ret[$i]))
	            {
	                if ((strpos($ret[$i], 'CREATE TABLE') !== false) && (strpos($ret[$i], 'DEFAULT CHARSET='. str_replace('-', '', $this->db_charset) )=== false))
	                {
	                    /* 建表时缺 DEFAULT CHARSET=utf8 */
	                    $ret[$i] = $ret[$i] . ' DEFAULT CHARSET='. str_replace('-', '', $this->db_charset);
	                }
	                $this->db->query($ret[$i]);
	            }
	        }
	    }
	    else
	    {
	        for($i = 0; $i < $ret_count; $i++)
	        {
	            $ret[$i] = trim($ret[$i], " \r\n,;"); //剔除多余信息
	            if ((strpos($ret[$i], 'CREATE TABLE') !== false) && (strpos($ret[$i], 'DEFAULT CHARSET='. str_replace('-', '', $this->db_charset) )!== false))
	            {
	                $ret[$i] = str_replace('DEFAULT CHARSET='. str_replace('-', '', $this->db_charset), '', $ret[$i]);
	            }
	            if (!empty($ret[$i]))
	            {
	                $this->db->query($ret[$i]);
	            }
	        }
	    }
	    return true;
	}

}
// End  Class

/* End of file SqlDump.class.php */
/* Location: /var/www/qqas/App/Lib/Util/SqlDump.class.php */
